feat(local): add --verify, --timeout, auto-detect dev script, post-init verification#998
Conversation
…it verification - Add src/lib/dev-script.ts: auto-detects dev command from package.json (dev > develop > serve > start), manage.py, app.py, main.py, go.mod, docker-compose.yml/compose.yml - Update sentry local run: when no command args provided, auto-detect from the project. Add --verify flag (wait for first SDK event, then exit) and --timeout flag (kill child after N seconds) - Add src/lib/init/verify-setup.ts: after successful sentry init, run the detected dev command with --verify to confirm SDK sends events. On failure, capture a Sentry event with diagnostic context. - Wire verify-setup into wizard-runner.ts handleFinalResult() - 21 tests across 3 files (dev-script unit + property, run command)
|
|
fix-ci: attempt 1 — tests use |
Tests were using /tmp/opencode which only exists in the local dev environment. CI runners don't have this directory, causing mkdtemp to fail. Switch to TEST_TMP_DIR from test/constants.ts which uses os.tmpdir() and is worker-scoped for parallel test isolation.
Codecov Results 📊❌ Patch coverage is 46.38%. Project has 4758 uncovered lines. Files with missing lines (4)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
- Coverage 82.00% 81.58% -0.42%
==========================================
Files 367 369 +2
Lines 25537 25829 +292
Branches 16750 16884 +134
==========================================
+ Hits 20939 21071 +132
- Misses 4598 4758 +160
- Partials 1760 1770 +10Generated by Codecov Action |
…p scripts with env vars - Store and clearTimeout after Promise.race resolves in runWithVerify and verifySetup to prevent holding the event loop alive - Add SIGINT/SIGTERM forwarding in runWithVerify so Ctrl-C kills the child instead of orphaning it - Detect shell features (env-var prefixes, pipes, operators) in package.json scripts and run them via sh -c instead of naive whitespace splitting
- Register SIGINT/SIGTERM handlers in verifySetup so Ctrl-C during post-init verification kills the child instead of orphaning it - Await child.exited after SIGTERM in both verifySetup and runWithVerify (envelope/timeout branches) so the child releases its port before the function returns
- Always add a timeout racer in runWithVerify — defaults to 30s when no explicit --timeout is given, preventing indefinite hangs - Redact KEY=VALUE env-var assignments in the detectedCommand telemetry field to avoid leaking secrets from package.json scripts
Use named handler references and removeListener after the race settles so SIGINT/SIGTERM aren't swallowed during teardown.
Prevents indefinite hangs when the dev server doesn't respond to SIGTERM. Extracted gracefulKill helper in run.ts; inlined the same pattern in verify-setup.ts.
|
fix-ci: attempt 1 — two lint errors (numeric separators |
- Replace 5_000 with 5000 to satisfy biome useNumericSeparators rule - Skip all-asterisk inputs in maskToken identity test (masking '*' correctly returns '*')
|
pushed fix in 02b8918 — removed |
Store and clearTimeout the SIGKILL grace timer so it doesn't hold the event loop alive for 5s after a cooperative child exit.
|
fix-ci: attempt 3 — biome flagged |
…n env redaction - Add $ and lowercase letters to SHELL_FEATURES_RE so scripts with variable references or mixed-case env assignments route through sh -c - Wrap gracefulKill's initial SIGTERM in try/catch so an already-exited child doesn't skip shutdownServer - Broaden telemetry redaction regex to match mixed-case env var names
Move removeListener calls after the child kill/await so SIGINT during the 5s grace period still forwards to the child.
- Clear the watchChildOutput timer when settle() is called from any path (fatal error, close event), preventing a 15s stall on exit. - Broaden ENV_VAR_RE to KEY_VALUE_RE to also catch --flag=value CLI arguments (e.g. --api-key=secret) in telemetry redaction. - Fix URI_USERINFO_RE to handle empty-username URIs like redis://:password@host by allowing zero chars before the colon. - Reuse scrubOutputLine() for detectedCommand telemetry field instead of a separate inline regex.
- Add exitCode check before awaiting close after SIGKILL in gracefulKill() to prevent indefinite hang if the close event fires between the kill call and listener attachment. - Scrub the error line shown to the user via ui.log.warn with scrubOutputLine() to redact credentials that may appear in startup errors (e.g. database connection strings in CI logs).
- cleanupChild now awaits the close event after SIGKILL so child.exitCode is populated before the crash detection check in verifySetup. Without this, a force-killed process would have exitCode=null, bypassing the started→exited correction. - Fix biome formatting (single-line if condition).
- Read child.exitCode before cleanupChild() so the crash detection sees the natural exit code, not 143/137 from our SIGTERM/SIGKILL. Fixes false failures where a healthy server killed by cleanup was reported as crashed. - Wrap verifySetup() call in wizard-runner with try-catch so unexpected throws from third-party calls (createSpotlightBuffer, buildApp) don't leave the spinner running or skip formatResult.
- Trim package.json script value before testing against SHELL_FEATURES_RE so leading whitespace doesn't prevent detection of env-var assignments (e.g. ' NODE_OPTIONS=... tsx dev'). - Remove unused biome-ignore suppression for useMaxParams in verify-setup.ts (reportOutcome now takes 2 params via ctx object). - Remove unused biome-ignore suppression for noControlCharactersInRegex in local.ts (biome no longer flags this regex). - Fix import sort order in wizard-runner.ts after adding logger.
There was a problem hiding this comment.
Unhandled child process error event in verifySetup causes process crash
In src/lib/init/verify-setup.ts, the spawned child process has no error event listener; an async spawn error (ENOENT, EACCES, etc.) will become an uncaught exception and crash the CLI. Add a child.on('error', ...) handler before entering the Promise.race.
Evidence
verify-setup.tsattacheschild.on('close')viachildExited(and indirectly viawatchChildOutput), but grep finds zerochild.on("error")registrations in the file.watchChildOutputonly registerschild.stdout/stderr dataandchild.on('close')listeners, leaving theerrorevent unhandled.- On Linux, a missing executable does not always throw synchronously from
spawn(); the error surface is equivalent to the same bug inrun.ts:runWithVerify. - Node.js
EventEmitterpromotes an unhandlederrorevent to an uncaught exception.
Identified by Warden find-bugs
Change ^[A-Za-z_]+= to ^[A-Za-z_]\w*= so scripts like E2E_BASE_URL=... or API_V2_KEY=... are correctly detected as needing shell execution instead of being whitespace-split.
Extend the post-race exit code correction to also cover the 'silent' outcome. A child that crashes immediately with no output would resolve as 'silent' (from watchChildOutput's close handler) instead of 'exited'. Now both 'started' and 'silent' are corrected to 'exited' when preCleanupExitCode is non-zero.
…ss capture and -f ai filter (#1034) ## Summary `sentry local` works well, but a few behaviors were undocumented and tripped up a user debugging logs/traces locally. This started as a docs fix and now also closes a real gap in client-side wiring. - **No DSN required.** With no DSN configured, events flow **only** to the local server — nothing reaches the user's Sentry org and no production quota is used. With a DSN set, the SDK sends to both. This is the main reassurance for using spotlight in local dev and wasn't stated. - **Inject every framework client prefix (behavior change).** `sentry local run` previously injected only `NEXT_PUBLIC_SENTRY_SPOTLIGHT`. It now injects the spotlight URL under **all** common framework client prefixes so the URL is inlined into the browser bundle regardless of bundler: - `PUBLIC_` (SvelteKit, Astro, Qwik) - `NEXT_PUBLIC_` (Next.js) - `VITE_` (Vite) - `NUXT_PUBLIC_` (Nuxt) - `REACT_APP_` (Create React App) - `VUE_APP_` (Vue CLI) - `GATSBY_` (Gatsby) This mirrors the prefix set from [getsentry/sentry-javascript#18198](getsentry/sentry-javascript#18198). `SENTRY_SPOTLIGHT` (read by server-side SDKs) is still set and is applied last so the base name is never shadowed by a client variant. - **Client-side wiring caveat, reframed.** No `@sentry/*` package reads these client vars *yet* — only `@sentry/node-core` reads `SENTRY_SPOTLIGHT` (server-side). The previous docs framed manual wiring as a permanent requirement; it's actually the **current workaround until the browser SDK reads these vars automatically** (#18198, which this PR is a good opportunity to revive). Until then, reference the variable for your framework: ```ts Sentry.init({ spotlight: process.env.NEXT_PUBLIC_SENTRY_SPOTLIGHT ?? false }); ``` - **`-f ai` filter.** Documented for watching `gen_ai`/`mcp` agent spans while iterating on an agent. Normalized `sentry local serve -f ai` → `sentry local -f ai` for consistency. ## Where it lands - `src/commands/local/run.ts` → `CLIENT_SPOTLIGHT_PREFIXES` constant + env injection loop; updated module JSDoc and `fullDescription`. - `test/commands/local/run.test.ts` → asserts every `<PREFIX>SENTRY_SPOTLIGHT` variant plus the base name are injected with the spotlight URL. - `docs/src/fragments/commands/local.md` → the `local` docs page + generated skill reference. - `docs/src/content/docs/agent-guidance.md` → the "Capture Events Locally" workflow. Since the skill generator keeps code blocks but strips prose, the key facts are encoded as comments in a bash snippet so they reach the agent skill too. - Regenerated `SKILL.md`. Unrelated date-based reference drift left out; CI auto-commit handles that. ## Notes - Complements #998 (which adds `--verify`/`--timeout`/dev-script auto-detect to `local run`). - `check:fragments`, `check:docs-sections`, `lint`, `typecheck`, and the `test/commands/local` suite (29 tests) pass locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Burak Yigit Kaya <byk@sentry.io> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
betegon
left a comment
There was a problem hiding this comment.
let's fix those conflicts and merge this thing!!!
|
fix-ci: attempt 1 — two lint issues after the merge-from-main:
Both fixed in 5e9f5bf. |
## Summary SIGINT/SIGTERM during `--verify` mode or post-init verification was forwarded to the child process but never re-emitted afterward. After cleanup completed, the CLI continued normally instead of terminating — the user's Ctrl+C was silently swallowed. This fix tracks the received signal and re-emits it via `process.kill(process.pid, signal)` after all cleanup (child kill, server shutdown, listener removal) completes, so the default handler terminates the process with the correct exit code. Fixes both `src/commands/local/run.ts` (`runWithVerify`) and `src/lib/init/verify-setup.ts` (`verifySetup`). ## Testing - `vitest run test/commands/local/run.test.ts` — 12 tests pass - `vitest run test/lib/init/wizard-runner.test.ts test/lib/dev-script.test.ts test/lib/dev-script.property.test.ts` — 57 tests pass - `pnpm run lint` — clean - `tsc --noEmit` — no errors (excluding pre-existing generated-file issues) Addresses warden findings from #998. <!-- ## Plan ### Problem Warden flagged (twice, across commits 5c40365 and fc94462) that SIGINT is swallowed in verify-setup.ts: signal handlers forward SIGINT to the child but don't re-emit or exit, so the wizard continues after verifySetup returns. The same pattern exists in runWithVerify in run.ts. ### Fix (2 files) 1. `src/lib/init/verify-setup.ts` - Add `signalReceived: NodeJS.Signals | null` variable - Update onSigint/onSigterm to set signalReceived before forwarding - After cleanup, check signalReceived and re-emit via process.kill(process.pid, signal) 2. `src/commands/local/run.ts` (runWithVerify function) - Same pattern: track signal, re-emit after cleanup ### Verification - PR #1034's CLIENT_SPOTLIGHT_PREFIXES work confirmed intact on main - All existing tests pass - Lint and typecheck clean -->
Summary
Adds dev script auto-detection and post-init verification to
sentry local run:dev>develop>serve>start), manage.py, app.py/main.py, go.mod, or docker-compose.yml--verifyflag: starts a local server, runs the app, waits for the first SDK envelope to arrive, then reports success/failure--timeoutflag: kills the child process after N secondssentry initsucceeds, automatically runs the detected dev command with--verify --timeout 30to confirm the SDK is sending events. On failure, captures a Sentry event with diagnostic context.Testing
bun run typecheck,check:deps,check:errors,check:fragmentsall pass